<?php
/* Database supported engines */
define('DATABASE_ENGINE_MYSQL',   1); // - _ They are both the same! ^_^
define('DATABASE_ENGINE_MARIADB', 1); // -

/* Database field types */
define('FIELD_TYPE_INTEGER', 1);
define('FIELD_TYPE_FLOAT',   2);
define('FIELD_TYPE_STRING',  3);

/*
 * DB Abstract Class Wrapper for YappFramework
 *
 * Singleton one instance class :)
 *
 */
abstract class yappDBWrapper
{

    // Relative to instance
    protected $instanceHash;

    // Relative to connection
    protected $hostName;
    protected $userName;
    protected $password;
    protected $port;
    protected $engine;

    // Relative to dabatases
    protected $typesCached       = false;
    protected $databases         = array();
    protected $defaultDatabase   = false;
    protected $currentDatabase   = false;
    protected $tables            = array();
    protected $types             = array();
    protected $cacheDir          = false;
    protected $transactionLevels = 0;

    // Relative to this calss
    protected $sqlHistory = array();

    // Static method to check if engine is support
    public static function engineSupported($engine)
    {
        // Add here when a new engine becomes supported
        $supported = false;
        switch($engine)
        {
            case DATABASE_ENGINE_MYSQL:
                $supported = 'mysql';
                break;
            case DATABASE_ENGINE_MARIADB:
                $supported = 'mysql';
                break;
        }
        return $supported;
    }

    /**
     * Database constructor
     * Receives and sets conf file
     **/
    public function  __construct($conf)
    {
        /*
         * Validate configuration
         */
        if (!empty($conf))
        {
            // Access
            if (!empty($conf['database_access']))
            {
                if (empty($conf['database_access']['host'])) die('No host for database');
                else $this->hostName = $conf['database_access']['host'];
                if (empty($conf['database_access']['username'])) die('No username for database');
                else $this->userName = $conf['database_access']['username'];
                if (!isset($conf['database_access']['password'])) die('No password for database');
                else $this->password = $conf['database_access']['password'];
                if (!empty($conf['database_access']['port'])) $this->port = $conf['database_access']['port'];
                if (empty($conf['database_access']['engine'])) die('No engine for database');
                else $this->engine = $conf['database_access']['engine'];
            }
            else
            {
                die("No database access configuration at all!");
            }
            // Databases
            if (!empty($conf['databases'])) $this->databases = $conf['databases'];
            else die("No databases were specified");
            // Default database
            if (!empty($conf['default_database'])) $this->defaultDatabase = $conf['default_database'];
            else die("No default database was selected");
            // Check if we will always build types
            if (empty($conf['always_build_types'])) $this->typesCached = true;
            // Check cache dir
            if (!empty($conf['cache_dir']))
            {
                if (is_dir($conf['cache_dir']))
                {
                    $this->cacheDir = $conf['cache_dir'];
                }
                else
                {
                    die("Database cache directory ({$conf['cache_dir']}) does not exist");
                }
            }
            else
            {
                die("Database cache dir is empty");
            }
        }
        else
        {
            die("No database configuration at all!");
        }
    }

    // Load types
    private function loadDatabaseTypes($dbName, $force = false)
    {
        $cacheDir  = $this->cacheDir.$dbName.'/';
        $cacheFile = $cacheDir.'types.php';
        if (((!$this->typesCached) && (empty($this->types[$dbName]))) || ($force == true))
        {
            $tablesAndFields       = $this->engineGetTablesAndFields();
            $this->tables[$dbName] = array_keys($tablesAndFields);
            $this->types[$dbName]  = $tablesAndFields;
            // Save in cache
            $serialized = '<?php $unserialize = \''.serialize($tablesAndFields).'\'; ?>';

            if (!is_dir($cacheDir))
            {
                if (!mkdir($cacheDir))
                {
                    die("Could not create cache directory for database. Please check writing permissons (1)");
                }
            }
            if ($fp = fopen($cacheFile, 'w'))
            {
                if (!fputs($fp, $serialized))
                {
                    die("Could not create cache directory for database. Please check writing permissons (3)");
                }
            }
            else
            {
                die("Could not create cache directory for database. Please check writing permissons (2)");
            }
        }
        else
        {
            if (file_exists($cacheFile))
            {
                require_once($cacheFile);
                $tablesAndFields       = unserialize($unserialize);
                $this->tables[$dbName] = array_keys($tablesAndFields);
                $this->types[$dbName]  = $tablesAndFields;
            }
            else
            {
                $this->loadDatabaseTypes($dbName, true);
            }
        }
    }

    // Get some table fields
    public function getTableFields($tableName)
    {
        if (isset($this->types[$this->currentDatabase][$tableName]))
        {
            return $this->types[$this->currentDatabase][$tableName];
        }
        else
        {
            return false;
        }
    }

    // Set instance hash
    public function setInstanceHash()
    {
        if (empty($this->instanceHash))
        {
            $this->instanceHash = md5(microtime());
        }
    }

    // Get instance hash
    public function getInstanceHash()
    {
        return $this->instanceHash;
    }

    // Set database
    public function setDatabase($dbName = false)
    {
        if (empty($dbName))
        {
            $dbName = $this->defaultDatabase;
        }
        if ((in_array($dbName, $this->databases)))
        {
            $this->checkConnection();
            if ($this->currentDatabase != $dbName)
            {
                if ($this->engineSetDatabase($dbName))
                {
                    $this->currentDatabase = $dbName;
                    // Check if need to load types
                    if (empty($this->types[$dbName]))
                    {
                        $this->loadDatabaseTypes($dbName);
                    }
                }
                else
                {
                    die("Failed selecting $dbName database!");
                }
            }
        }
        else
        {
            die("Invalid database");
        }
    }

    // Perform raw query. Use safely!
    public function rawQuery($sql)
    {
        $this->checkConnection();
        $this->sqlHistory[] = $sql;
        return $this->engineRawQuery($sql);
    }

    // Fetch associate array from result of rawQuery
    public function fetchAssoc($result)
    {
        return $this->engineFetchAssoc($result);
    }

    // Fetch plain array from result of rawQuery
    public function fetchRow($result)
    {
        return $this->engineFetchRow($result);
    }

    // Return num rows from result of rawQuery of select
    public function numRows($result)
    {
        return $this->engineNumRows($result);
    }

    // Create select
    public function select()
    {
        if (empty($this->currentDatabase)) return false;
        $args = func_get_args();
        $select = new Select($this->types, $this->currentDatabase, $this);
        $select->fields($args);
        return $select;
    }

    // Create Insert
    public function insertInto($tableName)
    {
        $insert = new Insert($this->types, $this->currentDatabase, $this);
        $insert->into($tableName);
        return $insert;
    }

    // Create update
    public function update($tableName)
    {
        $update = new Update($this->types, $this->currentDatabase, $this);
        $update->table($tableName);
        return $update;
    }

    // Affected rows
    public function affectedRows()
    {
        return $this->engineAffectedRows();
    }

    public function insertId()
    {
        return $this->engineInsertId();
    }

    // Get sql history
    public function getHistory()
    {
        return $this->sqlHistory;
    }

    // Check if given table exists
    public function tableExists($tableName = false)
    {
        if (empty($this->currentDatabase)) return false;
        return (!empty($this->types[$this->currentDatabase][$tableName]));
    }

    // Perform db built int escape tring
    public function escape($val)
    {
        return $this->engineEscape($val);
    }

    // Get offset and limit
    public function offsetAndLimit($totalOrOffset, $total = false)
    {
        return $this->engineOffsetAndLimit($totalOrOffset, $total);
    }

    // Get table primary key (or first field )
    public function getTablePk($tableName)
    {
        if ($this->tableExists($tableName))
        {
            reset($this->types[$this->currentDatabase][$tableName]);
            return key($this->types[$this->currentDatabase][$tableName]);
        }
        else
        {
            return false;
        }
    }

    // Transaction functions
    public function beginTransaction()
    {
        if (empty($this->transactionLevels))
        {
            $this->engineBeginTransaction();
            $this->transactionLevels++;
        }
        else
        {
            $this->transactionLevels++;
        }
    }
    public function rollbackTransaction()
    {
        if (!empty($this->transactionLevels))
        {
            $this->transactionLevels--;
            if (empty($this->transactionLevels))
            {
                $this->engineRollbackTransaction();
            }
        }
    }
    public function commitTransaction()
    {
        if (!empty($this->transactionLevels))
        {
            $this->transactionLevels--;
            if (empty($this->transactionLevels))
            {
                $this->engineCommitTransaction();
            }
        }
    }

    // Abastracts private methods
    protected abstract function checkConnection();
    protected abstract function connect();
    protected abstract function engineSetDatabase($dbName = false);
    protected abstract function engineRawQuery($sql);
    protected abstract function engineGetTablesAndFields();
    protected abstract function engineFetchAssoc($result);
    protected abstract function engineFetchRow($result);
    protected abstract function engineNumRows($result);
    protected abstract function engineAffectedRows();
    protected abstract function engineInsertId();
    protected abstract function engineEscape($val);
    protected abstract function engineOffsetAndLimit($totalOrOffset, $total);
    protected abstract function engineBeginTransaction();
    protected abstract function engineRollbackTransaction();
    protected abstract function engineCommitTransaction();
    // Abstract public methods
    public abstract function closeConnection();
}

?>